/**
  *******************************************************************************
  * @author   正点原子@ALIENTEK
  *           技术论坛 {@link www.openedv.com}
  *
  * @file     alientek_delay.c
  * @brief    This file management delays through the SysTick's
  *           normal count mode(suitable for STM32F10x series).
  *           本文件使用 SysTick 的普通计数模式对延迟进行管理（适合 STM32F10x 系列）
  *
  * @note     delay_ms(u16 nms): Delay nms milliseconds.
  *           delay_us(u32 nus): Delay nus microsecond.
  *
  *******************************************************************************
  * Change Logs:
  *   Date           Author       Notes
  *   2010-01-01     ALIENTEK     the first version
  *                  v1.2         修正了中断中调用出现死循环的错误
  *                               防止延时不准确，采用 do while 结构！
  *
  *                  v1.3         增加了对 UCOSII 延时的支持。
  *                               如果使用 UCOSII，delay_init 会自动设置 SYSTICK 的值，使之与 UCOSII 的 TICKS_PER_SEC 对应。
  *                               delay_ms 和 delay_us也进行了针对 UCOSII 的改造。
  *                               delay_us 可以在 UCOSII 下使用，而且准确度很高，更重要的是没有占用额外的定时器。
  *                               delay_ms 在 UCOSII 下，可以当成 OSTimeDly 来用，在未启动ucos时，它采用delay_us实现，从而准确延时
  *                               可以用来初始化外设，在启动了 UCOSII 之后 delay_ms 根据延时的长短，选择 OSTimeDly 实现或者 delay_us 实现。
  *
  *   2011-09-29     v1.4         修改了使用 UCOSII，但是 UCOSII 未启动的时候，delay_ms 中中断无法响应的 bug。
  *   2012-09-02     v1.5         在 delay_us 加入 UCOSII 上锁，防止由于 UCOSII 打断 delay_us 的执行，可能导致的延时不准。
  *   2015-01-09     v1.6         在 delay_ms 加入 OSLockNesting 判断。
  *   2015-03-19     v1.7         修改 OS 支持方式，以支持任意 OS（不限于 UCOSII 和 UCOSIII，理论上任意 OS 都可以支持）
  *                               添加：delay_osrunning/delay_ostickspersec/delay_osintnesting 三个宏定义
  *                               添加：delay_osschedlock/delay_osschedunlock/delay_ostimedly 三个函数
  *   2015-05-19     v1.8         修正 UCOSIII 支持时的 2 个bug：
  *                                   delay_tickspersec 改为：delay_ostickspersec
  *                                   delay_intnesting 改为：delay_osintnesting
  *******************************************************************************
  * @attention
  *
  *  本程序只供学习使用，未经作者许可，不得用于其它任何用途
  *  版权所有，盗版必究。
  *  Copyright (c) 广州市星翼电子科技有限公司 2009-2023
  *  All rights reserved.
  *******************************************************************************
  */

#include "alientek_delay.h"
#include "alientek_sys.h"

// 如果需要使用OS,则包括下面的头文件即可.
#if SYSTEM_SUPPORT_OS
#include "includes.h"					//UCOSII 使用
#endif

/**
 * 延时 1us 需要 SysTick 的计数次数
 * 由于 SysTick 每计数一次需要一个时钟周期，则当时钟周期为 72MHz 时：
 *    1. SysTick 每计数一次的时间为：1/72 us
 *    2. 1us 需要的计数次数为：fac_us = 1 / (1/72) = 72
 */
static u32 fac_us = 0;  // us 延时倍乘数，SysTick 每计数一次需要的微秒数

#if SYSTEM_SUPPORT_OS
static u16 fac_ms=0;	  // ms 延时倍乘数，在 OS 下，代表每个节拍的 ms 数
#endif

#if SYSTEM_SUPPORT_OS   //如果SYSTEM_SUPPORT_OS定义了,说明要支持OS了(不限于UCOS).
//当delay_us/delay_ms需要支持OS的时候需要三个与OS相关的宏定义和函数来支持
//首先是3个宏定义:
//    delay_osrunning:用于表示OS当前是否正在运行,以决定是否可以使用相关函数
//delay_ostickspersec:用于表示OS设定的时钟节拍,delay_init将根据这个参数来初始哈systick
// delay_osintnesting:用于表示OS中断嵌套级别,因为中断里面不可以调度,delay_ms使用该参数来决定如何运行
//然后是3个函数:
//  delay_osschedlock:用于锁定OS任务调度,禁止调度
//delay_osschedunlock:用于解锁OS任务调度,重新开启调度
//    delay_ostimedly:用于OS延时,可以引起任务调度.

//本例程仅作UCOSII和UCOSIII的支持,其他OS,请自行参考着移植
//支持UCOSII
#ifdef 	OS_CRITICAL_METHOD						//OS_CRITICAL_METHOD定义了,说明要支持UCOSII
#define delay_osrunning		OSRunning			//OS是否运行标记,0,不运行;1,在运行
#define delay_ostickspersec	OS_TICKS_PER_SEC	//OS时钟节拍,即每秒调度次数
#define delay_osintnesting 	OSIntNesting		//中断嵌套级别,即中断嵌套次数
#endif

//支持UCOSIII
#ifdef 	CPU_CFG_CRITICAL_METHOD					//CPU_CFG_CRITICAL_METHOD定义了,说明要支持UCOSIII
#define delay_osrunning		OSRunning			//OS是否运行标记,0,不运行;1,在运行
#define delay_ostickspersec	OSCfg_TickRate_Hz	//OS时钟节拍,即每秒调度次数
#define delay_osintnesting 	OSIntNestingCtr		//中断嵌套级别,即中断嵌套次数
#endif


//us级延时时,关闭任务调度(防止打断us级延迟)
void delay_osschedlock(void)
{
#ifdef CPU_CFG_CRITICAL_METHOD   				//使用UCOSIII
  OS_ERR err;
  OSSchedLock(&err);							//UCOSIII的方式,禁止调度，防止打断us延时
#else											//否则UCOSII
  OSSchedLock();								//UCOSII的方式,禁止调度，防止打断us延时
#endif
}

//us级延时时,恢复任务调度
void delay_osschedunlock(void)
{
#ifdef CPU_CFG_CRITICAL_METHOD   				//使用UCOSIII
  OS_ERR err;
  OSSchedUnlock(&err);						//UCOSIII的方式,恢复调度
#else											//否则UCOSII
  OSSchedUnlock();							//UCOSII的方式,恢复调度
#endif
}

//调用OS自带的延时函数延时
//ticks:延时的节拍数
void delay_ostimedly(u32 ticks)
{
#ifdef CPU_CFG_CRITICAL_METHOD
  OS_ERR err;
  OSTimeDly(ticks,OS_OPT_TIME_PERIODIC,&err);	//UCOSIII延时采用周期模式
#else
  OSTimeDly(ticks);							//UCOSII延时
#endif
}

//systick中断服务函数,使用ucos时用到
void SysTick_Handler(void)
{
  if(delay_osrunning==1)						//OS开始跑了,才执行正常的调度处理
  {
    OSIntEnter();							//进入中断
    OSTimeTick();       					//调用ucos的时钟服务程序
    OSIntExit();       	 					//触发任务切换软中断
  }
}
#endif

/**
 * 初始化延迟函数
 *
 * 当使用 ucos 的时候，此函数会初始化 ucos 的时钟节拍。SYSTICK的时钟固定为AHB时钟
 * @param SYSCLK 系统时钟频率，单位：MHz
 */
void delay_init(u8 SYSCLK)
{
#if SYSTEM_SUPPORT_OS     // 如果需要支持OS.
  u32 reload;
#endif
  HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);// SysTick频率为HCLK
  fac_us = SYSCLK;        // 不论是否使用OS，fac_us 都需要使用

#if SYSTEM_SUPPORT_OS     // 如果需要支持OS.
  reload=SYSCLK;				  // 每秒钟的计数次数 单位为K
  reload*=1000000/delay_ostickspersec;	//根据delay_ostickspersec设定溢出时间
                      //reload为24位寄存器,最大值:16777216,在72M下,约合0.233s左右
  fac_ms=1000/delay_ostickspersec;		//代表OS可以延时的最少单位
  SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;//开启SYSTICK中断
  SysTick->LOAD=reload; 					//每1/OS_TICKS_PER_SEC秒中断一次
  SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK
#else
#endif
}

#if SYSTEM_SUPPORT_OS            // 如果需要支持OS.
//延时nus
//nus:要延时的us数.
//nus:0~190887435(最大值即2^32/fac_us@fac_us=22.5)
void delay_us(u32 nus)
{
  u32 ticks;
  u32 told,tnow,tcnt=0;
  u32 reload=SysTick->LOAD;				//LOAD的值
  ticks=nus*fac_us; 						//需要的节拍数
  delay_osschedlock();					//阻止OS调度，防止打断us延时
  told=SysTick->VAL;        				//刚进入时的计数器值
  while(1)
  {
    tnow=SysTick->VAL;
    if(tnow!=told)
    {
      if(tnow<told)tcnt+=told-tnow;	//这里注意一下SYSTICK是一个递减的计数器就可以了.
      else tcnt+=reload-tnow+told;
      told=tnow;
      if(tcnt>=ticks)break;			//时间超过/等于要延迟的时间,则退出.
    }
  };
  delay_osschedunlock();					//恢复OS调度
}
//延时nms
//nms:要延时的ms数
//nms:0~65535
void delay_ms(u16 nms)
{
  if(delay_osrunning&&delay_osintnesting==0)//如果OS已经在跑了,并且不是在中断里面(中断里面不能任务调度)
  {
    if(nms>=fac_ms)						//延时的时间大于OS的最少时间周期
    {
         delay_ostimedly(nms/fac_ms);	//OS延时
    }
    nms%=fac_ms;						//OS已经无法提供这么小的延时了,采用普通方式延时
  }
  delay_us((u32)(nms*1000));				//普通方式延时
}
#else  // 不用ucos时

/**
 * 延时 nus 微秒（时钟摘取法）
 *
 * @param nus 要延时的微秒：0~190887435(最大值为 2^32/fac_us@fac_us=22.5)
 */
void delay_us(u32 nus)
{
  const u32 reload = SysTick->LOAD; // LOAD 的值
  u32 cnt_val = 0;            // 已经经过的计数值
  u32 old_val = SysTick->VAL; // 计数器旧值
  u32 cur_val;                // 计数器当前值
  while (1)
  {
    cur_val = SysTick->VAL;
    if (cur_val == old_val) continue;

    // SysTick 是一个递减计数器，则：
    // 正常递减，且未递减到 0，cnt_val 需要加上新旧值之差，表示过去的时间
    if (cur_val < old_val) cnt_val += old_val - cur_val;
    // 递减到 0 后重新赋值，cnt_val 需要加上 old_val 到 0 的时间，以及从 reload 到 cur_val 的时间
    else cnt_val += reload - cur_val + old_val;

    old_val = cur_val;

    // 时间超过/等于要延迟的时间则退出
    if (cnt_val >= nus * fac_us) break;
  }
}

/**
 * 延时 nms 毫秒
 * @param nms 要延时的毫秒数
 */
void delay_ms(u16 nms)
{
  u32 i;
  for (i = 0; i < nms; i++) delay_us(1000);
}

#endif
